package com.edfu.core.coursemgt.service.impl;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.edfu.core.commons.beans.Category;
import com.edfu.core.commons.persistence.api.ICategoryMgtPersistence;
import com.edfu.core.commons.persistence.api.IResourceMgtPersistence;
import com.edfu.core.commons.persistence.entity.ResourceEntity;
import com.edfu.core.commons.service.api.INotificationService;
import com.edfu.core.contentmgt.jcr.service.api.IContentMgtService;
import com.edfu.core.coursemgt.persistence.api.ICourseOfferingMgtPersistence;
import com.edfu.core.coursemgt.persistence.entity.CourseOfferingCategoryRelnEntity;
import com.edfu.core.coursemgt.persistence.entity.CourseOfferingEntity;
import com.edfu.core.coursemgt.persistence.entity.CourseOfferingInstanceEntity;
import com.edfu.core.coursemgt.persistence.entity.CourseOfferingOwnerRelnEntity;
import com.edfu.core.coursemgt.persistence.entity.LessonEntity;
import com.edfu.core.coursemgt.persistence.entity.LessonUnitEntity;
import com.edfu.core.coursemgt.service.api.ICourseOfferingMgtService;
import com.edfu.core.coursemgt.util.ContentMgtHook;
import com.edfu.core.coursemgt.util.CourseMgtConstants;
import com.edfu.core.coursemgt.util.CourseMgtModelConverter;
import com.edfu.core.coursemgt.vo.Consts.CourseOfferingMode;
import com.edfu.core.coursemgt.vo.CourseOffering;
import com.edfu.core.coursemgt.vo.Lesson;
import com.edfu.core.coursemgt.vo.LessonUnit;
import com.edfu.core.identitymgt.persistence.api.IIdentityMgtPersistence;
import com.edfu.core.identitymgt.persistence.entity.UserEnrollmentEntity;
import com.edfu.core.identitymgt.persistence.entity.UserEntity;
import com.edfu.core.institutemgt.persistence.entity.BatchEntity;
import com.edfu.core.util.Response;

@Service
public class CourseOfferingMgtServiceImpl implements ICourseOfferingMgtService {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(CourseOfferingMgtServiceImpl.class);
	
	@Autowired
	private ICourseOfferingMgtPersistence courseOfferingMgtPersistence;

	@Autowired
	private IIdentityMgtPersistence identityMgtPersistence;
	
	@Autowired
	private INotificationService notificationService;	
	
	@Autowired
	private IResourceMgtPersistence resourceMgtPersistence;
	
	@Autowired
	private IContentMgtService contentMgtService;
	
	@Autowired
	private ICategoryMgtPersistence categoryMgtPersistence;

	@Autowired
	private ContentMgtHook contentMgtHook;
	
	public ContentMgtHook getContentMgtHook() {
		return contentMgtHook;
	}

	public void setContentMgtHook(ContentMgtHook contentMgtHook) {
		this.contentMgtHook = contentMgtHook;
	}

	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { RuntimeException.class })
	public CourseOffering addCourseOffering(CourseOffering courseOffering) {
		CourseOfferingEntity coe = CourseMgtModelConverter.getCourseOfferingEntity(courseOffering);
		Set<CourseOfferingOwnerRelnEntity> coOwnerRelnSet = new HashSet<CourseOfferingOwnerRelnEntity>();
		for ( String userId : courseOffering.getOwners() ) {
			UserEntity ue = identityMgtPersistence.getUser(userId);
			CourseOfferingOwnerRelnEntity coOwnerReln = new CourseOfferingOwnerRelnEntity();
			coOwnerReln.setCourseOffering(coe);
			coOwnerReln.setOwner(ue);
			coOwnerRelnSet.add(coOwnerReln);
			
		}
		if ( coe.getLessons() != null ) {
			for ( LessonEntity lesson : coe.getLessons() ) {
				lesson.setAuthor(identityMgtPersistence.getUser(lesson.getAuthor().getUserId()));
				lesson.setCourseOffering(coe);
			}
		}
		
		coe.setOwners(coOwnerRelnSet);
		coe.setContentDirectory(contentMgtHook.addCourseOfferingNode(coe));
		courseOfferingMgtPersistence.persist(coe);
		
		if(courseOffering.getCategories() != null && courseOffering.getCategories().size() > 0) {
			for ( Category category : courseOffering.getCategories() ) {
				CourseOfferingCategoryRelnEntity cocre = new CourseOfferingCategoryRelnEntity();
				cocre.setCategory(categoryMgtPersistence.getCategory(category.getId()));
				cocre.setCourseOffering(coe);
				courseOfferingMgtPersistence.persist(cocre);
			}
		}
		
		return CourseMgtModelConverter.getCourseOffering(coe);
	}

	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { RuntimeException.class })
	public Response deleteCourseOffering(String courseOfferingCode) {
		CourseOfferingEntity entity = courseOfferingMgtPersistence.getCourseOffering(courseOfferingCode);
		courseOfferingMgtPersistence.remove(entity);
		Response r = new Response();
		r.setSuccessful(true);
		return r;
	}

	@Override
	@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = { RuntimeException.class })
	public List<CourseOffering> getCourseOfferings() {
		List<CourseOffering> coList = new ArrayList<CourseOffering>();
		List<CourseOfferingEntity> coeList = courseOfferingMgtPersistence.getPublicallyAvailableCourseOfferings();
		if (coeList != null && coeList.size() > 0) {
			for (CourseOfferingEntity coe : coeList) {
				coList.add(CourseMgtModelConverter.getCourseOffering(coe));
			}
		}
		return coList;
	}

	@Override
	@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = { RuntimeException.class })
	public CourseOffering getCourseOffering(String courseOfferingCode) {
		CourseOffering co = null;
		CourseOfferingEntity coe = courseOfferingMgtPersistence.getCourseOffering(courseOfferingCode);
		if (coe != null) {
			co = CourseMgtModelConverter.getCourseOffering(coe);
		}
		return co;
	}

	@Override
	@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = { RuntimeException.class })
	public List<CourseOffering> getCourseOfferingsByAuthor(String authorId) {
		List<CourseOffering> coList = new ArrayList<CourseOffering>();
		List<CourseOfferingEntity> coeList = courseOfferingMgtPersistence.getCourseOfferingsByAuthor(authorId);
		if (coeList != null && coeList.size() > 0) {
			for (CourseOfferingEntity coe : coeList) {
				coList.add(CourseMgtModelConverter.getCourseOffering(coe));
			}
		}
		return coList;
	}

	@Override
	@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = { RuntimeException.class })
	public List<CourseOffering> getEnrolledCourseOfferings(String studentId) {
		List<CourseOffering> coList = new ArrayList<CourseOffering>();
		List<CourseOfferingInstanceEntity> coieList = courseOfferingMgtPersistence.getEnrolledCourseOfferings(studentId);
		if (coieList != null && coieList.size() > 0) {
			for (CourseOfferingInstanceEntity coie : coieList) {
				coList.add(CourseMgtModelConverter.getCourseOffering(coie.getCourseOffering()));
			}
		}
		return coList;
	}

	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { RuntimeException.class })
	public Lesson addLesson(Lesson lesson, String courseOfferingCode) {
		LessonEntity le = CourseMgtModelConverter.getLessonEntity(lesson);
		le.setCourseOffering(courseOfferingMgtPersistence.getCourseOffering(courseOfferingCode));
		le.setAuthor(identityMgtPersistence.getUser(lesson.getAuthor()));
		le.setContentDirectory(contentMgtHook.addLessonNode(le));
		courseOfferingMgtPersistence.persist(le);
		
		return CourseMgtModelConverter.getLesson(le);
	}
	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { RuntimeException.class })
	public Response updateLesson(Lesson lesson, String courseOfferingCode) {
		LessonEntity le = courseOfferingMgtPersistence.getLesson(courseOfferingCode, lesson.getName());
		le.setDescription(lesson.getDescription());
		le.setPrice(lesson.getPrice());
		le.setLessonNo(lesson.getLessonNo());
		le.setPublished(lesson.isPublished());
		le.setTitle(lesson.getTitle());
		courseOfferingMgtPersistence.merge(le);
		
		Response r = new Response();
		r.setSuccessful(true);
		return r;
	}

	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { RuntimeException.class })
	public Response deleteLesson(String courseOfferingCode, String lessonName) {
		LessonEntity entity = courseOfferingMgtPersistence.getLesson(courseOfferingCode, lessonName);
		courseOfferingMgtPersistence.remove(entity);
		Response r = new Response();
		r.setSuccessful(true);
		return r;
	}
	
	@Override
	@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = { RuntimeException.class })
	public List<Lesson> getLessons(String courseOfferingCode) {
		List<Lesson> lessonsList = new ArrayList<Lesson>();
		List<LessonEntity> lessonEntList = courseOfferingMgtPersistence.getLessons(courseOfferingCode);
		if(lessonEntList != null && lessonEntList.size() > 0) {
			for (LessonEntity lessonEnt : lessonEntList) {
				lessonsList.add(CourseMgtModelConverter.getLesson(lessonEnt));
			}
		}
		return lessonsList;
	}


	@Override
	@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = { RuntimeException.class })
	public Lesson getLesson(String courseOfferingCode, String lessonName) {
		LessonEntity lessonEnt = courseOfferingMgtPersistence.getLesson(courseOfferingCode, lessonName);
		if(lessonEnt == null)
			throw new RuntimeException("Invalid Course Offering Code AND/OR Lesson Name");
		return CourseMgtModelConverter.getLesson(lessonEnt);
	}			

	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { RuntimeException.class })
	public LessonUnit addLessonUnit(LessonUnit lessonUnit, String courseOfferingCode, String lessonName) {
		LessonUnitEntity lue = CourseMgtModelConverter.getLessonUnitEntity(lessonUnit);
		if ( lue.getParentUnit() != null ) {
			lue.setParentUnit(courseOfferingMgtPersistence.getLessonUnit(courseOfferingCode, lessonName, lue.getParentUnit().getName()));
		}
		lue.setLesson(courseOfferingMgtPersistence.getLesson(courseOfferingCode, lessonName));
		lue.setContentDirectory(contentMgtHook.addLessonUnitNode(lue));
		courseOfferingMgtPersistence.persist(lue);
		
		return CourseMgtModelConverter.getLessonUnit(lue);
	}
	
	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { RuntimeException.class })
	public Response updateLessonUnit(LessonUnit lessonUnit,
			String courseOfferingCode, String lessonName) {
		LessonUnitEntity lue = courseOfferingMgtPersistence.getLessonUnit(courseOfferingCode, lessonName, lessonUnit.getName());
		if (lue == null)
			throw new RuntimeException("Lesson Unit with name " + lessonUnit.getName() + " not found");
		lue.setDescription(lessonUnit.getDescription());
		lue.setTitle(lessonUnit.getTitle());
		lue.setUnitNo(lessonUnit.getUnitNo());
		courseOfferingMgtPersistence.merge(lue);
		
		Response r = new Response();
		r.setSuccessful(true);
		return r;

	}
	
	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { RuntimeException.class })
	public Response deleteLessonUnit(String courseOfferingCode,
			String lessonName, String unitName) {
		LessonUnitEntity entity = courseOfferingMgtPersistence.getLessonUnit(courseOfferingCode, lessonName, unitName);
		courseOfferingMgtPersistence.remove(entity);
		Response r = new Response();
		r.setSuccessful(true);
		return r;
	}

	@Override
	@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = { RuntimeException.class })
	public LessonUnit getLessonUnit(String courseOfferingCode,
			String lessonName, String lessonUnitName) {
		LessonUnitEntity unitEnt = courseOfferingMgtPersistence.getLessonUnit(courseOfferingCode, lessonName, lessonUnitName);
		if(unitEnt == null)
			throw new RuntimeException("Invalid Course Offering Code AND/OR Lesson Name AND/OR Lesson Unit Name");
		return CourseMgtModelConverter.getLessonUnit(unitEnt);
	}


	@Override
	@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = { RuntimeException.class })
	public List<LessonUnit> getLessonUnits(String courseOfferingCode,
			String lessonName) {
		List<LessonUnit> unitsList = new ArrayList<LessonUnit>();
		List<LessonUnitEntity> unitEntList = courseOfferingMgtPersistence.getLessonUnits(courseOfferingCode, lessonName);
		if(unitEntList != null && unitEntList.size() > 0) {
			for(LessonUnitEntity unitEnt : unitEntList) {
				unitsList.add(CourseMgtModelConverter.getLessonUnit(unitEnt));
			}
		}
		return CourseMgtModelConverter._getHierarchicalList(unitsList);
	}

	@Override
	@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = { RuntimeException.class })
	public List<CourseOffering> getAuthoredCourseOfferings() {
		//TODO Get Logged in User ID from Identity Service
		String loggedInUserID = "sahsharm";
		return getCourseOfferingsByAuthor(loggedInUserID);
	}


	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { RuntimeException.class })
	public void addContent(InputStream contentAsStream,
			String contentCtxType, String contentContextId, String contentType, String contentId,
			Map<String, String> propertiesMap, Map<String,String> parentContextMap) {
		
		String ctxPath = null;
		long parentId;
		String parentType ="";
		
		if (contentCtxType.equalsIgnoreCase(CourseMgtConstants.CourseContentContextType.LESSON.toString())) {
			if (parentContextMap == null || !parentContextMap.containsKey("courseOfferingCode") ) {
				throw new RuntimeException("Invalid Course Offering Code passed in parent context map");
			}
			String courseOfferingCode = parentContextMap.get("courseOfferingCode");
			LessonEntity le = courseOfferingMgtPersistence.getLesson(contentContextId, courseOfferingCode);
			if ( le == null ) {
				throw new RuntimeException("Invalid Content Context Id");
			}
			ctxPath = le.getContentDirectory();
			parentId = le.getId();
			parentType = CourseMgtConstants.CourseContentContextType.LESSON.toString();
		}else if (contentCtxType.equalsIgnoreCase(CourseMgtConstants.CourseContentContextType.COURSEOFFERING.toString())) {
			CourseOfferingEntity coe = courseOfferingMgtPersistence.getCourseOffering(contentContextId);
			if ( coe == null ) {
				throw new RuntimeException("Invalid Content Context Id");
			}
			ctxPath = coe.getContentDirectory();
			parentId = coe.getId();
			parentType = CourseMgtConstants.CourseContentContextType.COURSEOFFERING.toString();

		}else if (contentCtxType.equalsIgnoreCase(CourseMgtConstants.CourseContentContextType.LESSON_UNIT.toString())) {
			if (parentContextMap == null || !parentContextMap.containsKey("courseOfferingCode") ) {
				throw new RuntimeException("Invalid Course Offering Code passed in parent context map");
			}
			if (!parentContextMap.containsKey("lessonName")) {
				throw new RuntimeException("Invalid Lesson Name passed in parent context map");
			}
			String courseOfferingCode = parentContextMap.get("courseOfferingCode");
			String lessonName = parentContextMap.get("lessonName");
			LessonUnitEntity lue = courseOfferingMgtPersistence.getLessonUnit(courseOfferingCode, lessonName, contentContextId);
			if ( lue == null ) {
				throw new RuntimeException("Invalid Content Context Id");
			}
			ctxPath = lue.getContentDirectory();
			parentId = lue.getId();
			parentType = CourseMgtConstants.CourseContentContextType.LESSON_UNIT.toString();

		}else {
			throw new RuntimeException("Invalid Content Context Type");
		}
		
		String resourceFullyQualifiedName = contentMgtHook.addContent(contentAsStream, ctxPath, contentType, contentId, propertiesMap);
		ResourceEntity re = new ResourceEntity();
		re.setName(propertiesMap.get("RESOURCE_NAME"));
		re.setExposed(false);
		re.setDescription(propertiesMap.get("RESOURCE_DESC"));
		re.setUuid(contentMgtService.getFileDetailsFromPath(resourceFullyQualifiedName).getContentUUID());
		resourceMgtPersistence.addResource(re);
		resourceMgtPersistence.mapResource(re.getId(), parentId, parentType);
	}
	
	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { RuntimeException.class })
	public CourseOffering updateCourseOffering(CourseOffering courseOffering) {
		CourseOfferingEntity coe = courseOfferingMgtPersistence.getCourseOffering(courseOffering.getCode());
		coe.setDescription(courseOffering.getDescription());
		coe.setFree(courseOffering.isFree());
		coe.setPrice(courseOffering.getPrice());
		coe.setLanguage(courseOffering.getLanguage());
		coe.setMode(CourseOfferingMode.ONLINE);
		coe.setTitle(courseOffering.getTitle());
		coe.setVisibility(courseOffering.getVisibility());
		courseOfferingMgtPersistence.merge(coe);
		return CourseMgtModelConverter.getCourseOffering(coe);
	}

	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { RuntimeException.class })
	public List<CourseOffering> addAuthors(String courseOfferingCode,
			List<String> authorIds) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { RuntimeException.class })
	public void enrollToCourseOffering(String courseOfferingCode, String userId) {
		
		UserEntity ue = identityMgtPersistence.getUser(userId);
		if(ue == null)
			throw new IllegalArgumentException("Invalid User ID");
		
		CourseOfferingEntity coe = courseOfferingMgtPersistence.getCourseOffering(courseOfferingCode);
		if(coe == null)
			throw new IllegalArgumentException("Invalid Course Offering Code");
		
		CourseOfferingInstanceEntity coie = courseOfferingMgtPersistence.getCourseOfferingInstance(courseOfferingCode);
		
		if(coie == null) {
			coie = new CourseOfferingInstanceEntity();
			
			BatchEntity be = new BatchEntity();
			be.setCode(new Long(System.currentTimeMillis()).toString());
			be.setCreatedOn(new Date());
			be.setLastModifiedOn(new Date());

			UserEnrollmentEntity uee = new UserEnrollmentEntity();
			uee.setBatch(be);
			
			uee.setUser(identityMgtPersistence.getUser(userId));
			uee.setCreatedOn(new Date());
			uee.setLastModifiedOn(new Date());
			uee.setRole(identityMgtPersistence.getRole("STUDENT"));
			uee.setDeleted(false);
			
			be.setEnrollments(new HashSet<UserEnrollmentEntity>());
			be.getEnrollments().add(uee);
			be.setTitle("Enrollment " + coe.getTitle());
			courseOfferingMgtPersistence.persist(be);
			
			coie.setCourseOffering(coe);
			coie.setBatch(be);
			
			//TODO
			coie.setOwner(((CourseOfferingOwnerRelnEntity)(coe.getOwners().toArray()[0])).getOwner());
			
			courseOfferingMgtPersistence.persist(coie);
		}else {
			Set<UserEnrollmentEntity> ueeSet = coie.getBatch().getEnrollments();
			//Ensure that user is not already enrolled
			for(UserEnrollmentEntity uee : ueeSet) {
				if(uee.getUser().getUserId().equalsIgnoreCase(userId)) {
					LOGGER.warn("User " + userId  + " already Enrolled for offering " + courseOfferingCode);
					throw new RuntimeException("User Already Enrolled");
				}
			}
			//Add the new user to the existing batch
			UserEnrollmentEntity uee = new UserEnrollmentEntity();
			uee.setBatch(coie.getBatch());
			uee.setUser(identityMgtPersistence.getUser(userId));
			uee.setCreatedOn(new Date());
			uee.setLastModifiedOn(new Date());
			uee.setRole(identityMgtPersistence.getRole("STUDENT"));
			uee.setDeleted(false);
			ueeSet.add(uee);
			courseOfferingMgtPersistence.merge(coie);
		}
	}
	
	
	


}
